Librerias

library(readxl)
Error in exists(cacheKey, where = .rs.WorkingDataEnv, inherits = FALSE) : 
  invalid first argument
library(tidyverse) # manipulacion de datos
Error in exists(cacheKey, where = .rs.WorkingDataEnv, inherits = FALSE) : 
  invalid first argument
library(ggplot2) # graficos
Error in exists(cacheKey, where = .rs.WorkingDataEnv, inherits = FALSE) : 
  invalid first argument
library(magrittr) # %>%
Error in exists(cacheKey, where = .rs.WorkingDataEnv, inherits = FALSE) : 
  invalid first argument
library(dplyr)
Error in exists(cacheKey, where = .rs.WorkingDataEnv, inherits = FALSE) : 
  invalid first argument
library(rpart)
Error in exists(cacheKey, where = .rs.WorkingDataEnv, inherits = FALSE) : 
  invalid first argument
library(caret)
Error in exists(cacheKey, where = .rs.WorkingDataEnv, inherits = FALSE) : 
  invalid first argument

Importar datos

Datos_MOD1 <- read_excel("default of credit card clients.xls")
Error in exists(cacheKey, where = .rs.WorkingDataEnv, inherits = FALSE) : 
  invalid first argument
Error in assign(cacheKey, frame, .rs.CachedDataEnv) : 
  attempt to use zero-length variable name
View(Datos_MOD1)

Exploración data ————

Ver la estructura de la variable y convertir a factor


names(Datos_MOD1)[which(names(Datos_MOD1) == "default payment next month")] <-"default"
Error in exists(cacheKey, where = .rs.WorkingDataEnv, inherits = FALSE) : 
  invalid first argument
Error in assign(cacheKey, frame, .rs.CachedDataEnv) : 
  attempt to use zero-length variable name
Datos_MOD1 %$% str(default)
 num [1:30000] 1 1 0 0 0 0 0 0 0 0 ...
Datos_MOD1 %<>% 
  mutate( default = factor(default, 
                     levels= c("1","0"), 
                     labels= c("si", "no"))) -> Datos_MOD1
Error in exists(cacheKey, where = .rs.WorkingDataEnv, inherits = FALSE) : 
  invalid first argument
Error in assign(cacheKey, frame, .rs.CachedDataEnv) : 
  attempt to use zero-length variable name
Datos_MOD1 %<>% 
  mutate( SEX = factor(SEX),
          EDUCATION = factor(EDUCATION),
          MARRIAGE = factor(MARRIAGE)) -> Datos_MOD1
Error in exists(cacheKey, where = .rs.WorkingDataEnv, inherits = FALSE) : 
  invalid first argument
Error in assign(cacheKey, frame, .rs.CachedDataEnv) : 
  attempt to use zero-length variable name
Datos_MOD1 %<>% 
  mutate( PAY_0 = factor(PAY_0), 
          PAY_2= factor(PAY_2),
          PAY_3= factor(PAY_3),
          PAY_4= factor(PAY_4),
          PAY_5= factor(PAY_5),
          PAY_6= factor(PAY_6)) -> Datos_MOD1
Error in exists(cacheKey, where = .rs.WorkingDataEnv, inherits = FALSE) : 
  invalid first argument
Error in assign(cacheKey, frame, .rs.CachedDataEnv) : 
  attempt to use zero-length variable name

Explorar el Balanceo


Datos_MOD1 %>%
group_by(default) %>%
  summarise( Frec= n()) %>%
  mutate(Prop= Frec/ sum(Frec) ) 
NA

Resumen de los datos


summary(Datos_MOD1)
   LIMIT_BAL       SEX       EDUCATION MARRIAGE       AGE            PAY_0           PAY_2           PAY_3      
 Min.   :  10000   1:11888   0:   14   0:   54   Min.   :21.00   0      :14737   0      :15730   0      :15764  
 1st Qu.:  50000   2:18112   1:10585   1:13659   1st Qu.:28.00   -1     : 5686   -1     : 6050   -1     : 5938  
 Median : 140000             2:14030   2:15964   Median :34.00   1      : 3688   2      : 3927   -2     : 4085  
 Mean   : 167484             3: 4917   3:  323   Mean   :35.49   -2     : 2759   -2     : 3782   2      : 3819  
 3rd Qu.: 240000             4:  123             3rd Qu.:41.00   2      : 2667   3      :  326   3      :  240  
 Max.   :1000000             5:  280             Max.   :79.00   3      :  322   4      :   99   4      :   76  
                             6:   51                             (Other):  141   (Other):   86   (Other):   78  
     PAY_4           PAY_5           PAY_6         BILL_AMT1         BILL_AMT2        BILL_AMT3         BILL_AMT4      
 0      :16455   0      :16947   0      :16286   Min.   :-165580   Min.   :-69777   Min.   :-157264   Min.   :-170000  
 -1     : 5687   -1     : 5539   -1     : 5740   1st Qu.:   3559   1st Qu.:  2985   1st Qu.:   2666   1st Qu.:   2327  
 -2     : 4348   -2     : 4546   -2     : 4895   Median :  22382   Median : 21200   Median :  20089   Median :  19052  
 2      : 3159   2      : 2626   2      : 2766   Mean   :  51223   Mean   : 49179   Mean   :  47013   Mean   :  43263  
 3      :  180   3      :  178   3      :  184   3rd Qu.:  67091   3rd Qu.: 64006   3rd Qu.:  60165   3rd Qu.:  54506  
 4      :   69   4      :   84   4      :   49   Max.   : 964511   Max.   :983931   Max.   :1664089   Max.   : 891586  
 (Other):  102   (Other):   80   (Other):   80                                                                         
   BILL_AMT5        BILL_AMT6          PAY_AMT1         PAY_AMT2          PAY_AMT3         PAY_AMT4     
 Min.   :-81334   Min.   :-339603   Min.   :     0   Min.   :      0   Min.   :     0   Min.   :     0  
 1st Qu.:  1763   1st Qu.:   1256   1st Qu.:  1000   1st Qu.:    833   1st Qu.:   390   1st Qu.:   296  
 Median : 18105   Median :  17071   Median :  2100   Median :   2009   Median :  1800   Median :  1500  
 Mean   : 40311   Mean   :  38872   Mean   :  5664   Mean   :   5921   Mean   :  5226   Mean   :  4826  
 3rd Qu.: 50191   3rd Qu.:  49198   3rd Qu.:  5006   3rd Qu.:   5000   3rd Qu.:  4505   3rd Qu.:  4013  
 Max.   :927171   Max.   : 961664   Max.   :873552   Max.   :1684259   Max.   :896040   Max.   :621000  
                                                                                                        
    PAY_AMT5           PAY_AMT6        default   
 Min.   :     0.0   Min.   :     0.0   si: 6636  
 1st Qu.:   252.5   1st Qu.:   117.8   no:23364  
 Median :  1500.0   Median :  1500.0             
 Mean   :  4799.4   Mean   :  5215.5             
 3rd Qu.:  4031.5   3rd Qu.:  4000.0             
 Max.   :426529.0   Max.   :528666.0             
                                                 
table(Datos_MOD1$SEX)

    1     2 
11888 18112 
table(Datos_MOD1$EDUCATION)

    0     1     2     3     4     5     6 
   14 10585 14030  4917   123   280    51 
table(Datos_MOD1$MARRIAGE)

    0     1     2     3 
   54 13659 15964   323 
table(Datos_MOD1$PAY_0, useNA = "ifany")

   -2    -1     0     1     2     3     4     5     6     7     8 
 2759  5686 14737  3688  2667   322    76    26    11     9    19 
table(Datos_MOD1$PAY_2, useNA = "ifany")

   -2    -1     0     1     2     3     4     5     6     7     8 
 3782  6050 15730    28  3927   326    99    25    12    20     1 
table(Datos_MOD1$PAY_3, useNA = "ifany")

   -2    -1     0     1     2     3     4     5     6     7     8 
 4085  5938 15764     4  3819   240    76    21    23    27     3 
table(Datos_MOD1$PAY_4, useNA = "ifany")

   -2    -1     0     1     2     3     4     5     6     7     8 
 4348  5687 16455     2  3159   180    69    35     5    58     2 
table(Datos_MOD1$PAY_5, useNA = "ifany")

   -2    -1     0     2     3     4     5     6     7     8 
 4546  5539 16947  2626   178    84    17     4    58     1 
table(Datos_MOD1$PAY_6, useNA = "ifany")

   -2    -1     0     2     3     4     5     6     7     8 
 4895  5740 16286  2766   184    49    13    19    46     2 
table(Datos_MOD1$default)

   si    no 
 6636 23364 
mean(Datos_MOD1$AGE)
[1] 35.4855

Visualización de Datos


# Crear un histograma para la columna "AGE" (numérica)
hist(Datos_MOD1$AGE, main = "Histograma de Edades", xlab = "Edad")


# Crear un gráfico de barras para variables categóricas (factor)
barplot(table(Datos_MOD1$SEX), main = "Distribución de Sexo")

barplot(table(Datos_MOD1$EDUCATION), main = "Distribución de Educación")

barplot(table(Datos_MOD1$MARRIAGE), main = "Distribución de Estado Civil")

NA
NA

Valores atípicos


boxplot(Datos_MOD1$LIMIT_BAL, main = "Boxplot de LIMIT_BAL")

Correlaciones


# Calcular la matriz de correlación para variables numéricas
correlation_matrix <- cor(Datos_MOD1[, c("LIMIT_BAL", "AGE", "BILL_AMT1", "BILL_AMT2", "BILL_AMT3", "BILL_AMT4", "BILL_AMT5", "BILL_AMT6", "PAY_AMT1", "PAY_AMT2", "PAY_AMT3", "PAY_AMT4", "PAY_AMT5", "PAY_AMT6")])

# Visualizar la matriz de correlación
heatmap(correlation_matrix)

NA
NA
NA

Partición Train - Test


set.seed(1234) # Semilla para aleatorios
datos_split <- Datos_MOD1 %>%
  initial_split(prop = 0.8,
                strata = default)
train <- training(datos_split)
dim(train)
[1] 23999    24
test <- testing(datos_split)
dim(test)
[1] 6001   24

Preprocesamiento


rct_data <- train %>% recipe(default ~ ., data = ., role= "predictor" ) %>%
  step_normalize( all_numeric(), -all_outcomes()) %>% # Normalizacion
  step_other(all_nominal(), -all_outcomes() ) %>% 
  step_dummy(all_nominal(), -all_outcomes() ) %>% # Dummy
  step_nzv(all_predictors()) %>% 
  themis::step_upsample(y, over_ratio = 0.9, skip= TRUE, seed= 123) %>% 
  step_sample(size = 5000, skip = TRUE, seed= 456 )
Warning: Selectors are not used for this step.

Remuestreo


set.seed(1234)
cv_data <- vfold_cv(train, v = 5, repeats = 1, strata = default)
cv_data
#  5-fold cross-validation using stratification 

Arbol de decision


# Crear el modelo de Árbol de Decisión
tree_model <- rpart(default ~ ., data = Datos_MOD1)

Grid de busqueda


# Definir la malla de búsqueda de hiperparámetros
grid <- expand.grid(cp = seq(0.01, 0.1, by = 0.01))

# Realizar la búsqueda de hiperparámetros utilizando validación cruzada
tree_model <- train(default ~ ., data = Datos_MOD1, method = "rpart", tuneGrid = grid, trControl = trainControl(method = "cv"))

# Visualizar los resultados de la búsqueda de hiperparámetros
print(tree_model)
CART 

30000 samples
   23 predictor
    2 classes: 'si', 'no' 

No pre-processing
Resampling: Cross-Validated (10 fold) 
Summary of sample sizes: 27000, 27001, 27000, 27001, 27000, 27000, ... 
Resampling results across tuning parameters:

  cp    Accuracy   Kappa    
  0.01  0.8168002  0.3343625
  0.02  0.8128335  0.3086768
  0.03  0.8128335  0.3086768
  0.04  0.8128335  0.3086768
  0.05  0.8128335  0.3086768
  0.06  0.8128335  0.3086768
  0.07  0.8128335  0.3086768
  0.08  0.8128335  0.3086768
  0.09  0.8128335  0.3086768
  0.10  0.8128335  0.3086768

Accuracy was used to select the optimal model using the largest value.
The final value used for the model was cp = 0.01.

Entrenamiento con el valor óptimo


optimal_cp <- 0.1
tree_model <- rpart(default ~ ., data = train, control = rpart.control(cp = optimal_cp))

predictions <- predict(tree_model, newdata = test, type = "class")

# Evaluación del modelo en el grupo 'test'
confusion_matrix <- table(predictions, test$default)
accuracy <- sum(diag(confusion_matrix)) / sum(confusion_matrix)

# Visualizar la matriz de confusión
print(confusion_matrix)
           
predictions   si   no
         si  431  205
         no  897 4468
# Mostrar la precisión del modelo
print(paste("Accuracy:", accuracy))
[1] "Accuracy: 0.816363939343443"

Calcular sensibilidad


sensitivity <- confusion_matrix[2, 2] / sum(confusion_matrix[2, ])
specificity <- confusion_matrix[1, 1] / sum(confusion_matrix[1, ])
print(paste("Sensitivity:", sensitivity))
[1] "Sensitivity: 0.832805219012116"
print(paste("Specificity:", specificity))
[1] "Specificity: 0.677672955974843"

En resumen, el modelo es efectivo, ya que nos da una explicación alta a los datos que queremos encontrar mediante el uso

de árboles de decisión, optimizando sus hiperparámetros con el uso de una grid

LS0tDQp0aXRsZTogIlRhcmVhIE03IFMyIg0KYXV0aG9yOiAiQWxlamFuZHJvIFZlbGFzY28gQyINCm91dHB1dDoNCiAgcGRmX2RvY3VtZW50OiBkZWZhdWx0DQogIGh0bWxfbm90ZWJvb2s6IGRlZmF1bHQNCi0tLQ0KDQojIyBMaWJyZXJpYXMNCg0KDQpgYGB7cn0NCmxpYnJhcnkocmVhZHhsKQ0KbGlicmFyeSh0aWR5dmVyc2UpICMgbWFuaXB1bGFjaW9uIGRlIGRhdG9zDQpsaWJyYXJ5KGdncGxvdDIpICMgZ3JhZmljb3MNCmxpYnJhcnkobWFncml0dHIpICMgJT4lDQpsaWJyYXJ5KGRwbHlyKQ0KbGlicmFyeShycGFydCkNCmxpYnJhcnkoY2FyZXQpDQpsaWJyYXJ5KHJzYW1wbGUpDQpsaWJyYXJ5KHJlY2lwZXMpDQpgYGANCg0KDQoNCg0KIyMgSW1wb3J0YXIgZGF0b3MNCg0KDQpgYGAge3J9DQpEYXRvc19NT0QxIDwtIHJlYWRfZXhjZWwoImRlZmF1bHQgb2YgY3JlZGl0IGNhcmQgY2xpZW50cy54bHMiKQ0KVmlldyhEYXRvc19NT0QxKQ0KDQpgYGANCiMjIEV4cGxvcmFjacOzbiBkYXRhIC0tLS0tLS0tLS0tLQ0KDQojIyBWZXIgbGEgZXN0cnVjdHVyYSBkZSBsYSB2YXJpYWJsZSB5IGNvbnZlcnRpciBhIGZhY3Rvcg0KYGBge3J9DQoNCm5hbWVzKERhdG9zX01PRDEpW3doaWNoKG5hbWVzKERhdG9zX01PRDEpID09ICJkZWZhdWx0IHBheW1lbnQgbmV4dCBtb250aCIpXSA8LSJkZWZhdWx0Ig0KDQpEYXRvc19NT0QxICUkJSBzdHIoZGVmYXVsdCkNCg0KRGF0b3NfTU9EMSAlPD4lIA0KICBtdXRhdGUoIGRlZmF1bHQgPSBmYWN0b3IoZGVmYXVsdCwgDQogICAgICAgICAgICAgICAgICAgICBsZXZlbHM9IGMoIjEiLCIwIiksIA0KICAgICAgICAgICAgICAgICAgICAgbGFiZWxzPSBjKCJzaSIsICJubyIpKSkgLT4gRGF0b3NfTU9EMQ0KDQpEYXRvc19NT0QxICU8PiUgDQogIG11dGF0ZSggU0VYID0gZmFjdG9yKFNFWCksDQogICAgICAgICAgRURVQ0FUSU9OID0gZmFjdG9yKEVEVUNBVElPTiksDQogICAgICAgICAgTUFSUklBR0UgPSBmYWN0b3IoTUFSUklBR0UpKSAtPiBEYXRvc19NT0QxDQoNCkRhdG9zX01PRDEgJTw+JSANCiAgbXV0YXRlKCBQQVlfMCA9IGZhY3RvcihQQVlfMCksIA0KICAgICAgICAgIFBBWV8yPSBmYWN0b3IoUEFZXzIpLA0KICAgICAgICAgIFBBWV8zPSBmYWN0b3IoUEFZXzMpLA0KICAgICAgICAgIFBBWV80PSBmYWN0b3IoUEFZXzQpLA0KICAgICAgICAgIFBBWV81PSBmYWN0b3IoUEFZXzUpLA0KICAgICAgICAgIFBBWV82PSBmYWN0b3IoUEFZXzYpKSAtPiBEYXRvc19NT0QxDQoNCg0KYGBgDQoNCiMjIEV4cGxvcmFyIGVsIEJhbGFuY2VvDQoNCmBgYHtyfQ0KDQpEYXRvc19NT0QxICU+JQ0KZ3JvdXBfYnkoZGVmYXVsdCkgJT4lDQogIHN1bW1hcmlzZSggRnJlYz0gbigpKSAlPiUNCiAgbXV0YXRlKFByb3A9IEZyZWMvIHN1bShGcmVjKSApIA0KDQpgYGANCiMjIFJlc3VtZW4gZGUgbG9zIGRhdG9zDQoNCmBgYHtyfQ0KDQpzdW1tYXJ5KERhdG9zX01PRDEpDQoNCnRhYmxlKERhdG9zX01PRDEkU0VYKQ0KdGFibGUoRGF0b3NfTU9EMSRFRFVDQVRJT04pDQp0YWJsZShEYXRvc19NT0QxJE1BUlJJQUdFKQ0KdGFibGUoRGF0b3NfTU9EMSRQQVlfMCwgdXNlTkEgPSAiaWZhbnkiKQ0KdGFibGUoRGF0b3NfTU9EMSRQQVlfMiwgdXNlTkEgPSAiaWZhbnkiKQ0KdGFibGUoRGF0b3NfTU9EMSRQQVlfMywgdXNlTkEgPSAiaWZhbnkiKQ0KdGFibGUoRGF0b3NfTU9EMSRQQVlfNCwgdXNlTkEgPSAiaWZhbnkiKQ0KdGFibGUoRGF0b3NfTU9EMSRQQVlfNSwgdXNlTkEgPSAiaWZhbnkiKQ0KdGFibGUoRGF0b3NfTU9EMSRQQVlfNiwgdXNlTkEgPSAiaWZhbnkiKQ0KdGFibGUoRGF0b3NfTU9EMSRkZWZhdWx0KQ0KDQptZWFuKERhdG9zX01PRDEkQUdFKQ0KDQpgYGANCg0KIyMgVmlzdWFsaXphY2nDs24gZGUgRGF0b3MNCg0KYGBge3J9DQoNCiMgQ3JlYXIgdW4gaGlzdG9ncmFtYSBwYXJhIGxhIGNvbHVtbmEgIkFHRSIgKG51bcOpcmljYSkNCmhpc3QoRGF0b3NfTU9EMSRBR0UsIG1haW4gPSAiSGlzdG9ncmFtYSBkZSBFZGFkZXMiLCB4bGFiID0gIkVkYWQiKQ0KDQojIENyZWFyIHVuIGdyw6FmaWNvIGRlIGJhcnJhcyBwYXJhIHZhcmlhYmxlcyBjYXRlZ8OzcmljYXMgKGZhY3RvcikNCmJhcnBsb3QodGFibGUoRGF0b3NfTU9EMSRTRVgpLCBtYWluID0gIkRpc3RyaWJ1Y2nDs24gZGUgU2V4byIpDQpiYXJwbG90KHRhYmxlKERhdG9zX01PRDEkRURVQ0FUSU9OKSwgbWFpbiA9ICJEaXN0cmlidWNpw7NuIGRlIEVkdWNhY2nDs24iKQ0KYmFycGxvdCh0YWJsZShEYXRvc19NT0QxJE1BUlJJQUdFKSwgbWFpbiA9ICJEaXN0cmlidWNpw7NuIGRlIEVzdGFkbyBDaXZpbCIpDQoNCg0KYGBgDQoNCg0KDQojIyBWYWxvcmVzIGF0w61waWNvcyANCg0KYGBge3J9DQoNCmJveHBsb3QoRGF0b3NfTU9EMSRMSU1JVF9CQUwsIG1haW4gPSAiQm94cGxvdCBkZSBMSU1JVF9CQUwiKQ0KDQpgYGANCg0KDQojIyBDb3JyZWxhY2lvbmVzDQoNCmBgYHtyfQ0KDQojIENhbGN1bGFyIGxhIG1hdHJpeiBkZSBjb3JyZWxhY2nDs24gcGFyYSB2YXJpYWJsZXMgbnVtw6lyaWNhcw0KY29ycmVsYXRpb25fbWF0cml4IDwtIGNvcihEYXRvc19NT0QxWywgYygiTElNSVRfQkFMIiwgIkFHRSIsICJCSUxMX0FNVDEiLCAiQklMTF9BTVQyIiwgIkJJTExfQU1UMyIsICJCSUxMX0FNVDQiLCAiQklMTF9BTVQ1IiwgIkJJTExfQU1UNiIsICJQQVlfQU1UMSIsICJQQVlfQU1UMiIsICJQQVlfQU1UMyIsICJQQVlfQU1UNCIsICJQQVlfQU1UNSIsICJQQVlfQU1UNiIpXSkNCg0KIyBWaXN1YWxpemFyIGxhIG1hdHJpeiBkZSBjb3JyZWxhY2nDs24NCmhlYXRtYXAoY29ycmVsYXRpb25fbWF0cml4KQ0KDQoNCg0KYGBgDQoNCg0KIyMgUGFydGljacOzbiBUcmFpbiAtIFRlc3QNCg0KYGBge3J9DQoNCnNldC5zZWVkKDEyMzQpICMgU2VtaWxsYSBwYXJhIGFsZWF0b3Jpb3MNCmRhdG9zX3NwbGl0IDwtIERhdG9zX01PRDEgJT4lDQogIGluaXRpYWxfc3BsaXQocHJvcCA9IDAuOCwNCiAgICAgICAgICAgICAgICBzdHJhdGEgPSBkZWZhdWx0KQ0KdHJhaW4gPC0gdHJhaW5pbmcoZGF0b3Nfc3BsaXQpDQpkaW0odHJhaW4pDQoNCnRlc3QgPC0gdGVzdGluZyhkYXRvc19zcGxpdCkNCmRpbSh0ZXN0KQ0KDQpgYGANCg0KIyMgUHJlcHJvY2VzYW1pZW50bw0KDQpgYGB7cn0NCg0KcmN0X2RhdGEgPC0gdHJhaW4gJT4lIHJlY2lwZShkZWZhdWx0IH4gLiwgZGF0YSA9IC4sIHJvbGU9ICJwcmVkaWN0b3IiICkgJT4lDQogIHN0ZXBfbm9ybWFsaXplKCBhbGxfbnVtZXJpYygpLCAtYWxsX291dGNvbWVzKCkpICU+JSAjIE5vcm1hbGl6YWNpb24NCiAgc3RlcF9vdGhlcihhbGxfbm9taW5hbCgpLCAtYWxsX291dGNvbWVzKCkgKSAlPiUgDQogIHN0ZXBfZHVtbXkoYWxsX25vbWluYWwoKSwgLWFsbF9vdXRjb21lcygpICkgJT4lICMgRHVtbXkNCiAgc3RlcF9uenYoYWxsX3ByZWRpY3RvcnMoKSkgJT4lIA0KICB0aGVtaXM6OnN0ZXBfdXBzYW1wbGUoeSwgb3Zlcl9yYXRpbyA9IDAuOSwgc2tpcD0gVFJVRSwgc2VlZD0gMTIzKSAlPiUgDQogIHN0ZXBfc2FtcGxlKHNpemUgPSA1MDAwLCBza2lwID0gVFJVRSwgc2VlZD0gNDU2ICkNCg0KDQpgYGANCg0KIyMgUmVtdWVzdHJlbw0KDQpgYGB7cn0NCg0Kc2V0LnNlZWQoMTIzNCkNCmN2X2RhdGEgPC0gdmZvbGRfY3YodHJhaW4sIHYgPSA1LCByZXBlYXRzID0gMSwgc3RyYXRhID0gZGVmYXVsdCkNCmN2X2RhdGENCg0KYGBgDQoNCiMjIEFyYm9sIGRlIGRlY2lzaW9uDQoNCmBgYHtyfQ0KDQojIENyZWFyIGVsIG1vZGVsbyBkZSDDgXJib2wgZGUgRGVjaXNpw7NuDQp0cmVlX21vZGVsIDwtIHJwYXJ0KGRlZmF1bHQgfiAuLCBkYXRhID0gRGF0b3NfTU9EMSkNCg0KYGBgDQoNCiMjIEdyaWQgZGUgYnVzcXVlZGENCg0KYGBge3J9DQoNCiMgRGVmaW5pciBsYSBtYWxsYSBkZSBiw7pzcXVlZGEgZGUgaGlwZXJwYXLDoW1ldHJvcw0KZ3JpZCA8LSBleHBhbmQuZ3JpZChjcCA9IHNlcSgwLjAxLCAwLjEsIGJ5ID0gMC4wMSkpDQoNCiMgUmVhbGl6YXIgbGEgYsO6c3F1ZWRhIGRlIGhpcGVycGFyw6FtZXRyb3MgdXRpbGl6YW5kbyB2YWxpZGFjacOzbiBjcnV6YWRhDQp0cmVlX21vZGVsIDwtIHRyYWluKGRlZmF1bHQgfiAuLCBkYXRhID0gRGF0b3NfTU9EMSwgbWV0aG9kID0gInJwYXJ0IiwgdHVuZUdyaWQgPSBncmlkLCB0ckNvbnRyb2wgPSB0cmFpbkNvbnRyb2wobWV0aG9kID0gImN2IikpDQoNCiMgVmlzdWFsaXphciBsb3MgcmVzdWx0YWRvcyBkZSBsYSBiw7pzcXVlZGEgZGUgaGlwZXJwYXLDoW1ldHJvcw0KcHJpbnQodHJlZV9tb2RlbCkNCg0KDQpgYGANCg0KDQojIyBFbnRyZW5hbWllbnRvIGNvbiBlbCB2YWxvciDDs3B0aW1vDQoNCmBgYHtyfQ0KDQpvcHRpbWFsX2NwIDwtIDAuMQ0KdHJlZV9tb2RlbCA8LSBycGFydChkZWZhdWx0IH4gLiwgZGF0YSA9IHRyYWluLCBjb250cm9sID0gcnBhcnQuY29udHJvbChjcCA9IG9wdGltYWxfY3ApKQ0KDQpwcmVkaWN0aW9ucyA8LSBwcmVkaWN0KHRyZWVfbW9kZWwsIG5ld2RhdGEgPSB0ZXN0LCB0eXBlID0gImNsYXNzIikNCg0KIyBFdmFsdWFjacOzbiBkZWwgbW9kZWxvIGVuIGVsIGdydXBvICd0ZXN0Jw0KY29uZnVzaW9uX21hdHJpeCA8LSB0YWJsZShwcmVkaWN0aW9ucywgdGVzdCRkZWZhdWx0KQ0KYWNjdXJhY3kgPC0gc3VtKGRpYWcoY29uZnVzaW9uX21hdHJpeCkpIC8gc3VtKGNvbmZ1c2lvbl9tYXRyaXgpDQoNCiMgVmlzdWFsaXphciBsYSBtYXRyaXogZGUgY29uZnVzacOzbg0KcHJpbnQoY29uZnVzaW9uX21hdHJpeCkNCg0KIyBNb3N0cmFyIGxhIHByZWNpc2nDs24gZGVsIG1vZGVsbw0KcHJpbnQocGFzdGUoIkFjY3VyYWN5OiIsIGFjY3VyYWN5KSkNCg0KYGBgDQoNCiMjIENhbGN1bGFyIHNlbnNpYmlsaWRhZA0KDQpgYGB7cn0NCg0Kc2Vuc2l0aXZpdHkgPC0gY29uZnVzaW9uX21hdHJpeFsyLCAyXSAvIHN1bShjb25mdXNpb25fbWF0cml4WzIsIF0pDQpzcGVjaWZpY2l0eSA8LSBjb25mdXNpb25fbWF0cml4WzEsIDFdIC8gc3VtKGNvbmZ1c2lvbl9tYXRyaXhbMSwgXSkNCnByaW50KHBhc3RlKCJTZW5zaXRpdml0eToiLCBzZW5zaXRpdml0eSkpDQpwcmludChwYXN0ZSgiU3BlY2lmaWNpdHk6Iiwgc3BlY2lmaWNpdHkpKQ0KDQpgYGANCg0KIyMgRW4gcmVzdW1lbiwgZWwgbW9kZWxvIGVzIGVmZWN0aXZvLCB5YSBxdWUgbm9zIGRhIHVuYSBleHBsaWNhY2nDs24gYWx0YSBhIGxvcyBkYXRvcyBxdWUgcXVlcmVtb3MgZW5jb250cmFyIG1lZGlhbnRlIGVsIHVzbw0KIyMgZGUgw6FyYm9sZXMgZGUgZGVjaXNpw7NuLCBvcHRpbWl6YW5kbyBzdXMgaGlwZXJwYXLDoW1ldHJvcyBjb24gZWwgdXNvIGRlIHVuYSBncmlkDQoNCg0KDQoNCg0KDQo=